//////////////////////////////////////////////////////////////////////
// CubemapTmpl.h
//
// SHEN Fangyang
// me@shenfy.com
//
// Copyright (C) SHEN Fangyang, 2011, All rights reserved.
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
//  Brief Description
//
//	Cubemap image wrapper.
//
//////////////////////////////////////////////////////////////////////
#pragma once
#include <ImgTmpl.h>
#include <Logger.h>

namespace PRT
{
	//Declaration
	template<typename T>
	class CCubemapTmpl
	{
	public:
		CCubemapTmpl(void);
		virtual ~CCubemapTmpl(void);

		virtual bool LoadFromFile(TCHAR *fn) = 0;
		virtual bool WriteToFile(TCHAR *fn) = 0;

		UINT GetBPP(void);
		UINT GetWidth(void);
		UINT GetHeight(void);

		//access with 3D view direction vector
		//READ ONLY
		T tex3D(const float &x, const float &y, const float &z, const UINT &c);
		void tex3D(const float &x, const float &y, const float &z, T *c/*output*/);

		//access with plain 2D texcoord
		T &tex2D(const UINT &u, const UINT &v, const UINT &c);
		void tex2D(const UINT &u, const UINT &v, T *c/*output*/);

		static void BilinearWeight(float x, float y, float *w);


	protected:
		void ResetFaceSize(void);

	protected:
		w32lib::CImgTmpl<T> *m_pImg;
		UINT m_faceWidth, m_faceHeight;
		UINT m_faceCoord[6][2];
	};

	//Definition
	template<typename T>
	CCubemapTmpl<T>::CCubemapTmpl() :
		m_faceWidth(0),
		m_faceHeight(0),
		m_pImg(0)
	{
		memset(m_faceCoord, 0, sizeof(UINT) * 12);
	}

	template<typename T>
	CCubemapTmpl<T>::~CCubemapTmpl()
	{
		if (m_pImg)
			delete m_pImg;
	}

	template<typename T>
	T &CCubemapTmpl<T>::tex2D(const UINT &u, const UINT &v, const UINT &c)
	{
		//not checking m_pImg != 0 in release mode for performance concern
		_ASSERT(m_pImg != 0);
		return (*m_pImg)(u, v, c);
	}

	template<typename T>
	void CCubemapTmpl<T>::tex2D(const UINT &u, const UINT &v, T *c)
	{
		for (UINT i = 0; i < (m_pImg->GetBPP() / 8 / sizeof(T)); i++)
		{
			c[i] = (*m_pImg)(u, v, i);
		}
	}

	template<typename T>
	UINT CCubemapTmpl<T>::GetBPP()
	{
		return m_pImg->GetBPP();
	}

	template<typename T>
	UINT CCubemapTmpl<T>::GetWidth()
	{
		return m_pImg->GetWidth();
	}

	template<typename T>
	UINT CCubemapTmpl<T>::GetHeight()
	{
		return m_pImg->GetHeight();
	}

	//3D direction vector to 2D (u,v)
	template<typename T>
	void CCubemapTmpl<T>::Tex3DTo2D(const float &x, const float &y, const float &z, /*input*/
									float &u, float &v, UINT &faceId /*output*/)
	{
		float absx, absy, absz;
		float ma, sc, tc;

		absx = abs(x);
		absy = abs(y);
		absz = abs(z);

		if (absx > absy && absx > absz) //x is the major axis direction
		{
			ma = absx;
			if (x > 0.0f)
			{
				sc = -z; tc = -y;
				faceId = 0;
			}
			else
			{
				sc = z; tc = -y;
				faceId = 1;
			}
		}
		else if (absy > absx && absy > absz) //y is major axis direction
		{
			ma = absy;
			if (y > 0.0f)
			{
				sc = x; tc = z;
				faceId = 2;
			}
			else
			{
				sc = x; tc = -z;
				faceId = 3;
			}
		}
		else //z is major axis direction
		{
			ma = absz;
			if (z > 0.0f)
			{
				sc = x; tc = -y;
				faceId = 4;
			}
			else
			{
				sc = -x; tc = -y
				faceId = 5;
			}
		}

		//normalized 2D texture coordinate on [0, 1]
		u = (sc / ma + 1) / 2.0f;
		v = (tc / ma + 1) / 2.0f;
	}

	template<typename T>
	void CCubemapTmpl<T>::ResetFaceSize()
	{
		m_faceWidth = GetWidth() / 4;
		m_faceHeight = GetHeight() / 3;

		m_faceCoord[0][0] = 2 * m_faceWidth;
		m_faceCoord[0][1] = m_faceHeight;
		m_faceCoord[1][0] = 0;
		m_faceCoord[1][1] = m_faceHeight;
		m_faceCoord[2][0] = m_faceWidth;
		m_faceCoord[2][1] = 0;
		m_faceCoord[3][0] = m_faceWidth;
		m_faceCoord[3][1] = 2 * m_faceHeight;
		m_faceCoord[4][0] = m_faceWidth;
		m_faceCoord[4][1] = m_faceHeight;
		m_faceCoord[5][0] = 3 * m_faceWidth;
		m_faceCoord[5][1] = m_faceHeight;
	}

	template<typename T>
	T CCubemapTmpl<T>::tex3D(const float &x, const float &y, const float &z, const UINT &c)
	{
		float u = 0.0f, v = 0.0f;
		UINT faceId = 0;

		Tex3DTo2D(x, y, z, u, v, faceId);

		float xf, yf;
		UINT x0, y0, x1, y1;

		xf = u * m_faceWidth - 0.5f + m_faceCoord[faceId][0];
		yf = v * m_faceHeight - 0.5f + m_faceCoord[faceId][1];
		x0 = (UINT)floor(xf);
		y0 = (UINT)floor(yf);
		x1 = (UINT)ceil(xf);
		y1 = (UINT)ceil(yf);

		float w[4] = {0};
		ISampler::BilinearWeight(xf, yf, w);

		T result = (T)((*m_pImg)(x0, y0, c) * w[0]
		+ (*m_pImg)(x1, y0, c) * w[1]
		+ (*m_pImg)(x0, y1, c) * w[2]
		+ (*m_pImg)(x1, y1, c) * w[3]);

		return result;
	}

	template<typename T>
	void CCubemapTmpl<T>::tex3D(const float &x, const float &y, const float &z, T *c)
	{
		float u = 0.0f, v = 0.0f;
		UINT faceId = 0;

		Tex3DTo2D(x, y, z, u, v, faceId);

		float xf, yf;
		UINT x0, y0, x1, y1;

		xf = u * m_faceWidth - 0.5f + m_faceCoord[faceId][0];
		yf = v * m_faceHeight - 0.5f + m_faceCoord[faceId][1];
		x0 = (UINT)floor(xf);
		y0 = (UINT)floor(yf);
		x1 = (UINT)ceil(xf);
		y1 = (UINT)ceil(yf);

		float w[4] = {0};
		ISampler::BilinearWeight(xf, yf, w);

		UINT nChannel = GetBPP() / 8 / sizeof(T);
		for (int i = 0; i < nChannel; i++)
		{
			*(c + i) = (T)((*m_pImg)(x0, y0, i) * w[0]
			+ (*m_pImg)(x1, y0, i) * w[1]
			+ (*m_pImg)(x0, y1, i) * w[2]
			+ (*m_pImg)(x1, y1, i) * w[3]);
		}
	}
}